﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CafeOnline.MvcFramework.Interfaces;
using CafeOnline.MvcFramework.Web.Json;

namespace CafeOnline.MvcFramework.Grid {
    public class Column : ObjectOptions {
        #region IFields

        protected string m_columnName;
        protected string m_gridId;

        #endregion IFields
        #region IConstructors

        private Column()
            : base() {
            Classes = new List<string>();
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="columnName">Name of column, cannot be blank or set to 'subgrid', 'cb', and 'rn'</param>
        internal Column(string columnName)
            : this() {
            // Make sure columnname is not left blank
            if (string.IsNullOrWhiteSpace(columnName)) {
                throw new ArgumentException("No columnname specified");
            }

            // Make sure columnname is not part of the reserved names collection
            var reservedNames = new string[] { "subgrid", "cb", "rn" };

            if (reservedNames.Contains(columnName)) {
                throw new ArgumentException("Columnname '" + columnName + "' is reserved");
            }
            m_columnName = columnName;

            // Set index equal to columnname by default, can be overriden by setter
            this.Index = columnName;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="columnName">Name of column, cannot be blank or set to 'subgrid', 'cb', and 'rn'</param>
        public Column(string columnName, IColumnFactory factory)
            : this(columnName) {
            Factory = factory;
        }

        internal Column(string gridId, string columnName, IColumnFactory factory)
            : this(columnName, factory) {
            m_gridId = gridId;
        }

        #endregion IConstructors

        #region IProperties

        /// <summary>
        /// Gets the factory that has created this column.
        /// </summary>
        public IColumnFactory Factory {
            get;
            private set;
        }

        /// <summary>
        /// (align) Defines the alignment of the cell in the Body layer, not in header cell. Possible values: left, center, right. 
        /// </summary>
        public Align? Align {
            get;
            protected set;
        }

        /// <summary>
        /// (classes) This option allow to add classes to the column. If more than one class will be used a space should be set. 
        /// By example classes:'class1 class2' will set a class1 and class2 to every cell on that column. 
        /// In the grid css there is a predefined class ui-ellipsis which allow to attach ellipsis to a particular row. Also this will work in FireFox too. empty string 
        /// </summary>
        public List<string> Classes {
            get;
            protected set;
        }

        /// <summary>
        /// (name) Set the unique name in the grid for the column. This property is required. 
        /// As well as other words used as property/event names, the reserved words (which cannot be used for names) include subgrid, cb and rn.
        /// </summary>
        public string ColumnName {
            get {
                return m_columnName;
            }
        }

        /// <summary>
        /// (edittype) Defines the edit type for inline and form editing.
        /// </summary>
        public EditType? EditType {
            get;
            protected set;
        }

        /// <summary>
        /// (editable) 
        /// Defines if the field is editable. This option is used in cell, inline and form modules.
        /// </summary>
        public bool? Editable {
            get;
            protected set;
        }
        /// <summary>
        ///  (firstsortorder) If set to asc or desc, the column will be sorted in that direction on first sort.Subsequent sorts of the column will toggle as usual
        /// </summary>
        public SortOrder? FirstSortOrder {
            get;
            protected set;
        }

        /// <summary>
        /// (fixed) If set to true this option does not allow recalculation of the width of the column if shrinkToFit option is set to true. Also the width does not change if a setGridWidth method is used to change the grid width.
        /// </summary>
        public bool? FixedWidth {
            get;
            protected set;
        }

        /// <summary>
        /// Formatter with options given in parameters
        /// </summary>
        public KeyValuePair<Formatters, ObjectOptions>? Formatter {
            get;
            protected set;
        }

        /// <summary>
        /// Custom formatter and unformatter
        /// </summary>
        public CustomFormatter CustomFormatter {
            get;
            protected set;
        }

        /// <summary>
        /// (index) Set the index name when sorting. Passed as sidx parameter.
        /// </summary>
        public string Index {
            get;
            protected set;
        }

        /// <summary>
        /// (frozen) boolean If set to true determines that this column will be frozen after calling the setFrozenColumns method
        /// In such a case we must ensure that all columns are 
        /// </summary>
        public bool? Frozen {
            get;
            protected set;
        }

        /// <summary>
        /// (jsonmap) Defines the json mapping for the column in the incoming json string. See Retrieving Data  none 
        /// </summary>
        public string JsonMap {
            get;
            protected set;
        }

        /// <summary>
        /// Defines if this column is hidden at initialization.
        /// </summary>
        public bool? Hidden {
            get;
            protected set;
        }
        public bool? Key {
            get;
            protected set;
        }

        /// <summary>
        /// (label) When colNames array is empty, defines the heading for this column. If both the colNames array and this setting are empty, the heading for this column comes from the name property.
        /// </summary>
        public string Label {
            get;
            protected set;
        }

        /// <summary>
        /// (resizable) Defines if the column can be re sized  true 
        /// </summary>
        public bool? Resizeable {
            get;
            protected set;
        }

        /// <summary>
        /// (search) boolean When used in search modules, disables or enables searching on that column. Search Configuration true 
        /// </summary>
        public bool? Search {
            get;
            protected set;
        }

        /// <summary>
        /// stype string Determines the type of the element when searching. See Search Configuration text 
        /// </summary>
        public Searchtype? SearchType {
            get;
            protected set;
        }

        /// <summary>
        /// Initial value for the search
        /// </summary>
        public object SearchInit {
            get;
            protected set;
        }

        /// <summary>
        /// The differents terms used for searching when it is a combobox
        /// </summary>
        public IEnumerable<KeyValuePair<int, string>> SearchTerms {
            get;
            protected set;
        }

        /// <summary>
        /// (sortable) boolean Defines is this can be sorted. true 
        /// </summary>
        public bool? Sortable {
            get;
            protected set;
        }

        /// <summary>
        /// (title) If this option is false the title is not displayed in that column when we hover a cell with the mouse
        /// Use full when a popin is configured for this column.
        /// </summary>
        public bool? Title {
            get;
            protected set;
        }

        /// <summary>
        /// (width) Set the initial width of the column, in pixels. This value currently can not be set as percentage
        /// </summary>
        public int? Width {
            get;
            protected set;
        }

        /// <summary>
        /// Specify if this columns can be made visible or not.
        /// </summary>
        public bool? HideDlg {
            get;
            protected set;
        }

        public string CellAttr {
            get;
            protected set;
        }

        #endregion Properties

        /// <summary>
        /// Defines the alignment of the cell in the Body layer, not in header cell. 
        /// Possible values: left, center, right. (default: left)
        /// </summary>
        /// <param name="align">Alignment of column (center, right, left)</param>
        public Column SetAlign(Align align) {
            IsPopulated = false;
            this.Align = align;
            return this;
        }

        /// <summary>
        /// This option allow to add a class to to every cell on that column. In the grid css 
        /// there is a predefined class ui-ellipsis which allow to attach ellipsis to a 
        /// particular row. Also this will work in FireFox too.
        /// Multiple calls to this function are allowed to set multiple classes
        /// </summary>
        /// <param name="className">Classname</param>
        public Column AddClass(string className) {
            IsPopulated = false;
            this.Classes.Add(className);
            return this;
        }

        /// <summary>
        /// Sets formatter with default formatoptions (as set in language file)
        /// </summary>
        /// <param name="formatter">Formatter</param>
        public Column SetFormatter(Formatters formatter) {
            IsPopulated = false;
            if (CustomFormatter != null) {
                throw new Exception("You cannot set a formatter and a customformatter at the same time, please choose one.");
            }
            this.Formatter = new KeyValuePair<Formatters, ObjectOptions>(formatter, null);
            return this;
        }

        /// <summary>
        /// Sets formatter with formatoptions (see jqGrid documentation for more info on formatoptions)
        /// </summary>
        /// <param name="formatter">Formatter</param>
        /// <param name="formatOptions">Formatoptions</param>
        public Column SetFormatter(Formatters formatter, ObjectOptions formatOptions) {
            IsPopulated = false;
            if (CustomFormatter != null) {
                throw new Exception("You cannot set a formatter and a customformatter at the same time, please choose one.");
            }
            this.Formatter = new KeyValuePair<Formatters, ObjectOptions>(formatter, formatOptions);
            return this;
        }


        /// <summary>
        /// Sets custom formatter. Usually this is a function. When set in the formatter option 
        /// this should not be enclosed in quotes and not entered with () - 
        /// just specify the name of the function
        /// The following variables are passed to the function:
        /// 'cellvalue': The value to be formated (pure text).
        /// 'options': Object { rowId: rid, colModel: cm} where rowId - is the id of the row colModel is 
        /// the object of the properties for this column getted from colModel array of jqGrid
        /// 'rowobject': Row data represented in the format determined from datatype option. 
        /// If we have datatype: xml/xmlstring - the rowObject is xml node,provided according to the rules 
        /// from xmlReader If we have datatype: json/jsonstring - the rowObject is array, provided according to 
        /// the rules from jsonReader
        /// </summary>
        /// <param name="customFormatter"></param>
        /// <returns></returns>
        public Column SetCustomFormatter(CustomFormatter customFormatter) {
            if (this.Formatter.HasValue) {
                throw new Exception("You cannot set a formatter and a customformatter at the same time, please choose one.");
            }
            this.CustomFormatter = customFormatter;
            return this;
        }

        /// <summary>
        /// Set searchterms if search type of this column is set to type select
        /// </summary>
        /// <param name="searchTerms">Searchterm to add to dropdownlist</param>
        public Column SetSearchTerms(IEnumerable<KeyValuePair<int, string>> searchTerms) {
            this.SearchTerms = searchTerms;
            return this;
        }

        public Column SetHideDlg(bool hideDlg) {
            HideDlg = hideDlg;
            return this;
        }

        /// <summary>
        /// If set to asc or desc, the column will be sorted in that direction on first 
        /// sort.Subsequent sorts of the column will toggle as usual (default: null)
        /// </summary>
        /// <param name="firstSortOrder">First sort order</param>
        public Column SetFirstSortOrder(SortOrder firstSortOrder) {
            this.FirstSortOrder = firstSortOrder;
            return this;
        }

        /// <summary>
        /// If set to true this option does not allow recalculation of the width of the 
        /// column if shrinkToFit option is set to true. Also the width does not change 
        /// if a setGridWidth method is used to change the grid width. (default: false)
        /// </summary>
        /// <param name="fixedWidth">Indicates if width of column is fixed</param>
        public Column SetFixed(bool fixedWidth) {
            this.FixedWidth = fixedWidth;
            return this;
        }



        /// <summary>
        /// Defines if this column is hidden at initialization. (default: false)
        /// </summary>
        /// <param name="hidden">Boolean indicating if column is hidden</param>
        public Column SetHidden(bool hidden) {
            this.Hidden = hidden;
            return this;
        }

        /// <summary>
        /// Set the index name when sorting. Passed as sidx parameter. (default: Same as columnname)
        /// </summary>
        /// <param name="index">Name of index</param>
        public Column SetIndex(string index) {
            this.Index = index;
            return this;
        }

        public Column SetJsonMap(string jsonMap) {
            JsonMap = jsonMap;
            return this;
        }

        /// <summary>
        /// In case if there is no id from server, this can be set as as id for the unique row id. 
        /// Only one column can have this property. If there are more than one key the grid finds 
        /// the first one and the second is ignored. (default: false)
        /// </summary>
        /// <param name="key">Indicates if key is set</param>
        public Column SetKey(bool key) {
            this.Key = key;
            return this;
        }

        /// <summary>
        /// Defines the heading for this column. If empty, the heading for this column comes from the name property.
        /// </summary>
        /// <param name="label">Label name of column</param>
        public Column SetLabel(string label) {
            this.Label = label;
            return this;
        }

        /// <summary>
        /// Defines if the column can be resized (default: true)
        /// </summary>
        /// <param name="resizeable">Indicates if the column is resizable</param>
        public Column SetResizeable(bool resizeable) {
            this.Resizeable = resizeable;
            return this;
        }

        /// <summary>
        /// When used in search modules, disables or enables searching on that column. (default: true)
        /// </summary>
        /// <param name="search">Indicates if searching for this column is enabled</param>
        public Column SetSearch(bool search) {
            this.Search = search;
            return this;
        }

        /// <summary>
        /// Sets the searchtype of this column (text, select or datepicker) (default: text)
        /// Note: To use datepicker jQueryUI javascript should be included
        /// </summary>
        /// <param name="searchType">Search type</param>
        public Column SetSearchType(Searchtype searchType) {
            this.SearchType = searchType;
            return this;
        }

        /// <summary>
        /// Specify the initial value for the search
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public Column SetSearchInit(object value) {
            this.SearchInit = value;
            return this;
        }

        /// <summary>
        /// Indicates if column is sortable (default: true)
        /// </summary>
        /// <param name="sortable">Indicates if column is sortable</param>
        public Column SetSortable(bool sortable) {
            this.Sortable = sortable;
            return this;
        }

        /// <summary>
        /// If this option is false the title is not displayed in that column when we hover over a cell (default: true)
        /// </summary>
        /// <param name="title">Indicates if title is displayed when hovering over cell</param>
        public Column SetTitle(bool title) {
            this.Title = title;
            return this;
        }

        /// <summary>
        /// Set the initial width of the column, in pixels. This value currently can not be set as percentage (default: 150)
        /// </summary>
        /// <param name="width">Width in pixels</param>
        public Column SetWidth(int width) {
            this.Width = width;
            return this;
        }

        public Column SetCellAttr(string function) {
            this.CellAttr = function;
            return this;
        }

        public Column SetEditType(EditType editType) {
            this.EditType = editType;
            return this;
        }

        public override void Populate(SerializationContext context) {
            //Ensure that we start from clear list of references
            //Clear();
            this.Add("name", ColumnName);

            if (this.Align.HasValue) {
                this.Add("align", this.Align);
            }
            if (this.Classes.Count > 0) {
                this.Add("classes", string.Join(" ", (from c in Classes
                                                      select c)));
            }

            if (this.Frozen.HasValue) {
                this.Add("frozen", this.Frozen.Value);
            }

            if (this.EditType.HasValue) {
                this.Add("edittype", this.EditType.Value);
            }

            if (this.FirstSortOrder.HasValue) {
                this.Add("firstsortorder", this.FirstSortOrder.Value);
            }

            if (this.FixedWidth.HasValue) {
                this.Add("fixed", this.FixedWidth.Value);
            }

            if (this.Editable.HasValue) {
                this.Add("editable", Editable.Value);
            }

            if (this.Formatter.HasValue) {
                this.Add("formatter", this.Formatter.Value.Key);
                if (this.Formatter.Value.Value != null) {
                    this.Add("formatoptions", this.Formatter.Value.Value);
                }
            }

            if (this.CustomFormatter != null) {
                if (!string.IsNullOrEmpty(this.CustomFormatter.Format)) {
                    this.Add("formatter", new JavascriptFunction(this.CustomFormatter.Format));
                }
                if (!string.IsNullOrEmpty(this.CustomFormatter.UnFormat)) {
                    this.Add("unformat", new JavascriptFunction(this.CustomFormatter.UnFormat));
                }
                if (this.CustomFormatter.Options != null) {
                    this.Add("formatoptions", this.CustomFormatter.Options);
                }
            }

            if (!string.IsNullOrEmpty(Index)) {
                this.Add("index", Index);
            }

            if (!string.IsNullOrEmpty(JsonMap)) {
                this.Add("jsonmap", JsonMap);
            }

            if (Hidden.HasValue) {
                this.Add("hidden", Hidden.Value);
            }

            if (Key.HasValue) {
                this.Add("key", Key.Value);
            }

            if (!string.IsNullOrEmpty(Label)) {
                this.Add("label", Label);
            }

            if (this.Resizeable.HasValue) {
                this.Add("resizable", this.Resizeable.Value);
            }

            if (this.Sortable.HasValue) {
                this.Add("sortable", this.Sortable.Value);
            }

            if (this.Title.HasValue) {
                this.Add("title", this.Title.Value);
            }

            if (this.Width.HasValue) {
                this.Add("width", this.Width.Value);
            }

            if (this.HideDlg.HasValue) {
                this.Add("hidedlg", this.HideDlg.Value);
            }

            if (this.Search.HasValue) {
                this.Add("search", this.Search.Value);
            }

            if (this.SearchType.HasValue) {
                switch (this.SearchType.Value) {
                    case Searchtype.select: {
                            this.Add("stype", this.SearchType.Value);
                            ObjectOptions options = new ObjectOptions();

                            if ((this.SearchTerms != null) && (this.SearchTerms.Any())) {
                                options.Add("value", ":;" + string.Join(";", SearchTerms.Select(s => s.Key + ":" + s.Value)));
                            } else {
                                options.Add("value", ":");
                            }
                            if (this.SearchInit != null) {
                                options.Add("defaultValue", this.SearchInit);
                                options.Add("dataInit", new JavascriptFunction("function(el){$(el).val(" + this.SearchInit + ");}"));
                            }
                            this.Add("searchoptions", options);
                        }
                        break;
                    case Searchtype.datepicker: {
                            ObjectOptions options = new ObjectOptions();
                            // We do not pass gridID to onSelect function because we use grid many place
                            // And all instance of one grid type will be share the column structure
                            // So that the column only populate the first time when it's called.
                            // --> It will be marked the date picker try to call the first called grid.

                            options.Add("dataInit", new JavascriptFunction(String.Format("function(el){{$(el).datepicker({{changeYear:true, onSelect: function() {{var divId = $(el).closest('.ui-jqgrid').attr('id'); var gridId = divId.substring(5); var sgrid = $('#'+gridId)[0]; sgrid.triggerToolbar();}},dateFormat:'{0}'}});}}", Localization.Resources.DatePickerFormat)));
                            this.Add("searchoptions", options);
                        }
                        break;
                    case Searchtype.datetimepicker: {
                            ObjectOptions options = new ObjectOptions();
                            options.Add("dataInit", new JavascriptFunction(String.Format("function(el){{$(el).datetimepicker({{changeYear:true, onSelect: function() {{var divId = $(el).closest('.ui-jqgrid').attr('id'); var gridId = divId.substring(5);var sgrid = $('#'+ gridId)[0]; sgrid.triggerToolbar();}},dateFormat:'{0}', timeFormat: '{1}'}});}}", Localization.Resources.DatePickerFormat, Localization.Resources.TimePickerFormat)));
                            this.Add("searchoptions", options);

                        }
                        break;
                    case Searchtype.timepicker: {
                            ObjectOptions options = new ObjectOptions();
                            options.Add("dataInit", new JavascriptFunction(String.Format("function(el){{$(el).timepicker({{onSelect: function() {{var divId = $(el).closest('.ui-jqgrid').attr('id'); var gridId = divId.substring(5);var sgrid = $('#'+gridId)[0]; sgrid.triggerToolbar();}},timeFormat: '{0}'}});}}", Localization.Resources.TimePickerFormat)));
                            this.Add("searchoptions", options);
                        }
                        break;
                    case Searchtype.text:
                        this.Add("stype", this.SearchType.Value);
                        if (this.SearchInit != null) {
                            ObjectOptions searchOptions = new ObjectOptions();
                            searchOptions.Add("defaultValue", this.SearchInit);
                            searchOptions.Add("dataInit",
                                        new JavascriptFunction("function(el){$(el).val('" + this.SearchInit + "');}"));
                            this.Add("searchoptions", searchOptions);
                        }
                        break;
                }
            }

            if (!string.IsNullOrEmpty(CellAttr)) {
                this.Add("cellattr", new JavascriptFunction(String.Format(" function (rowId, val, rawObject, cm, rdata) {{ {0} }}", this.CellAttr)));
            }

            base.Populate(context);
        }
    }
}
